/**
 * Model.java
 */
package model;
import commands.model.*;
import commands.controller.*;
import java.util.*;
import java.util.concurrent.*;
import controller.Controller;
import dataTypes.*;
import dataTypes.DownloadFile.DownloadStateType;



/**
 * @author Michal Kolodziejski
 * 
 */
public class Model implements Runnable {
	/** Size of file's single part */
	public static final int partSize = 50000;
	/** Number of maximum active downloads */
//	private int maxActiveDownloadsNumber;
//	private int maxActiveConnectionsPerDownload;
	
	private GlobalSettings globalSettings;

	/** Blocking queue */
	private BlockingQueue<ModelCommand> queue;
	/** List of shared files */
	private List<File> sharedFiles;
	/** List of download files */
	private List<ModelDownloadFile> downloadFiles;
	
	/** Reference to the controller */
	private Controller controller;
	/** Configuration manager */
	private ConfigManager configManager;
	
	/**
	 * Constructor
	 */
	public Model(Controller controler){
		this.controller = controler;
		this.configManager = new ConfigManager();
		this.queue = new LinkedBlockingQueue<ModelCommand>();
		this.globalSettings = new GlobalSettings();
		this.sharedFiles = configManager.getSharedFilesList();
		this.downloadFiles = configManager.getDownloadFilesList();
		this.configManager.getGlobalSettings(this.globalSettings);
		this.checkDownloadFilesList();
		this.getSharedFilesList();
		this.getDownloadFilesList();
		this.getMaxActiveConnectionsNumber();
	}
	
	
	/**
	 * Method required by Runnable interface.
	 * Starts queue's main loop and passes commands to dipatch.
	 */
	public void run(){
		ModelCommand command;
		while(true){
			try{
				command = queue.take();
				dispatchAction(command);
			} catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}

	
	/**
	 * Adds a command to the queue.
	 * @param command a command to be added
	 * @deprecated use writeMsg instead 
	 */
	public void reportAction(ModelCommand command){
		queue.add(command);
	}
	
	
	/**
	 * Adds a command to the queue.
	 * @param command a command to be added
	 */
	public void writeMsg(ModelCommand command) throws InterruptedException{
		queue.put(command);
	}
	
	
	/* **************************
	 * 		PRIVATE METHODS
	 */
	
	/**
	 * Dispatches commands from ModelActionsQueue
	 */
	private void dispatchAction(ModelCommand command){
		switch(command.getCommandType()){
			case SEARCH:
				if(!(command instanceof MCSearch) || ((MCSearch)command).getSearchName()==null)
					return;
				searchFile(((MCSearch)command).getSearchName(), ((MCSearch)command).getMinSize(), ((MCSearch)command).getMaxSize(), ((MCSearch)command).getSearchID());
				break;
			case GET_SHARED_FILES_LIST:
				getSharedFilesList();
				break;
			case ADD_SHARED_FILE:
				if(!(command instanceof MCAddSharedFile) || ((MCAddSharedFile)command).getFile()==null)
					return;
				addSharedFile(((MCAddSharedFile)command).getFile());
				break;
			case REMOVE_SHARED_FILE:
				if(!(command instanceof MCRemoveSharedFile) || ((MCRemoveSharedFile)command).getFile()==null)
					return;
				removeSharedFile(((MCRemoveSharedFile)command).getFile());
				break;
			case GET_DOWNLOAD_FILES_LIST:
				getDownloadFilesList();
				break;
			case ADD_DOWNLOAD_FILE:
				if(!(command instanceof MCAddDownloadFile) || ((MCAddDownloadFile)command).getFile()==null)
					return;
				addDownloadFile(((MCAddDownloadFile)command).getFile());
				break;
			case REMOVE_DOWNLOAD_FILE:
				if(!(command instanceof MCRemoveDownloadFile) || ((MCRemoveDownloadFile)command).getFile()==null)
					return;
				removeDownloadFile(((MCRemoveDownloadFile)command).getFile());
				break;
			case START_DOWNLOAD_FILE:
				if(!(command instanceof MCStartDownloadFile) || ((MCStartDownloadFile)command).getFile()==null)
					return;
				startDownloadFile(((MCStartDownloadFile)command).getFile());
				break; 
			case STOP_DOWNLOAD_FILE:
				if(!(command instanceof MCStopDownloadFile) || ((MCStopDownloadFile)command).getFile()==null)
					return;
				stopDownloadFile(((MCStopDownloadFile)command).getFile());
				break;
			case MOVE_DOWNLOAD_FILE:
				if(!(command instanceof MCMoveDownloadFile) || ((MCMoveDownloadFile)command).getFile()==null)
					return;
				moveDownloadFile(((MCMoveDownloadFile)command).getFile(), ((MCMoveDownloadFile)command).getPosition());
				break;
			case GET_DOWNLOAD_FILE_HOSTS_LIST:
				if(!(command instanceof MCGetDownloadFileHostsList) || ((MCGetDownloadFileHostsList)command).getFile()==null)
					return;
				getDownloadFileHostsList(((MCGetDownloadFileHostsList)command).getFile());
				break; 
			case ADD_DOWNLOAD_FILE_HOST:
				if(!(command instanceof MCAddDownloadFileHost) || ((MCAddDownloadFileHost)command).getFile()==null || ((MCAddDownloadFileHost)command).getHost()==null)
					return;
				addDownloadFileHost(((MCAddDownloadFileHost)command).getFile(), ((MCAddDownloadFileHost)command).getHost());
				break;
			case REMOVE_DOWNLOAD_FILE_HOST:
				if(!(command instanceof MCRemoveDownloadFileHost) || ((MCRemoveDownloadFileHost)command).getFile()==null || ((MCRemoveDownloadFileHost)command).getHost()==null)
					return;
				removeDownloadFileHost(((MCRemoveDownloadFileHost)command).getFile(), ((MCRemoveDownloadFileHost)command).getHost());
				break;
			case GET_NEXT_LACKING_PART:
				if(!(command instanceof MCGetNextLackingPart) || ((MCGetNextLackingPart)command).getFile()==null)
					return;
				getNextLackingPart(((MCGetNextLackingPart)command).getFile());
				break;
			case GET_NEXT_HOST:
				if(!(command instanceof MCGetNextHost) || ((MCGetNextHost)command).getFile()==null)
					return;
				getNextHost(((MCGetNextHost)command).getFile());
				break;
			case GET_PART_OF_FILE:
				if(!(command instanceof MCGetPartOfFile) || ((MCGetPartOfFile)command).getFile()==null || ((MCGetPartOfFile)command).getPartNumber()<0)
					return;
				getPartOfFile(((MCGetPartOfFile)command).getFile(), ((MCGetPartOfFile)command).getPartNumber());
				break;
			case SAVE_PART_OF_FILE:
				if(!(command instanceof MCSavePartOfFile) || ((MCSavePartOfFile)command).getData()==null || ((MCSavePartOfFile)command).getFile()==null || ((MCSavePartOfFile)command).getPartNumber()<0)
					return;
				savePartOfFile(((MCSavePartOfFile)command).getFile(), ((MCSavePartOfFile)command).getPartNumber(), ((MCSavePartOfFile)command).getData());
				break;
			case SAVE_CONFIGURATION:
				saveConfiguration();
				break;
			case GET_MAX_ACTIVE_CONNECTIONS_NUMBER:
				getMaxActiveConnectionsNumber();
				break;
			case SET_MAX_ACTIVE_CONNECTIONS_NUMBER:
				if(!(command instanceof MCSetMaxActiveConnectionsNumber) || ((MCSetMaxActiveConnectionsNumber)command).getMaxActiveConnectionsNumber()<0 || ((MCSetMaxActiveConnectionsNumber)command).getMaxActiveConnectionsPerDownload()<0)
					return;
				setMaxActiveConnectionsNumber(((MCSetMaxActiveConnectionsNumber)command).getMaxActiveConnectionsNumber(), ((MCSetMaxActiveConnectionsNumber)command).getMaxActiveConnectionsPerDownload());
				break;
			case CLEAR_DOWNLOAD_FILE_HOSTS_LIST:
				if(!(command instanceof MCClearDownloadFileHostsList) || ((MCClearDownloadFileHostsList)command).getFile()==null)
					return;
				clearDownloadFileHostsList(((MCClearDownloadFileHostsList)command).getFile());
				break;
			case IS_SHARED_FILE:
				if(!(command instanceof MCIsSharedFile) || ((MCIsSharedFile)command).getFile()==null)
					return;
				isSharedFile(((MCIsSharedFile)command).getFile(), ((MCIsSharedFile)command).getId());
				break;
			default:
				break;
		}	
	}
	

	
	/**
	 * Searches for files which names include at least one of searchKeywords
	 * and sends to the Controller a Map with file details and hit ratio
	 * ((fitting keywords)/(search keywords)). 
	 * @param searchKeywords list of keywords
	 */
	private void searchFile(String searchName, long minSize, long maxSize, int searchID){
		System.out.println("[MODEL] Entering searchFile...");
		// make a list of search keywords
		List<String> searchKeywords = new ArrayList<String>();
		StringTokenizer searchNameTokenizer = new StringTokenizer(searchName, " \t\n.");
		while(searchNameTokenizer.hasMoreTokens()){
			searchKeywords.add(searchNameTokenizer.nextToken().toLowerCase());
		}
		
		// print 'em
		System.out.print("[MODEL] Searching for a file. Keywords: ");
		for(Iterator<String> it = searchKeywords.iterator(); it.hasNext();){
			String keyword = it.next();
			System.out.print(keyword+" ");
		}
		System.out.println();
		
		Map<File, Float> resultList = new HashMap<File, Float>();
		File resultFile;
		for(ListIterator<File> it = sharedFiles.listIterator(); it.hasNext();){
			File file = it.next();
			// check size
			if(file.getSize() < minSize || file.getSize() > maxSize)
				continue;

			String filename;
			if(file.getPath().lastIndexOf(File.separator)!=-1)
				filename = file.getPath().substring(file.getPath().lastIndexOf(File.separator)+1);
			else
				filename = file.getPath();
			
			// check name
			if(searchName.compareToIgnoreCase(filename) == 0){
				Float hitRate = new Float((float)1.0);
				resultFile = new File(file);
				resultFile.setPath(filename);
				resultList.put(resultFile, hitRate);
				System.out.println(" * Search hit! Filename: "+filename+", hit-rate: "+hitRate);
				continue;
			}
			
			String filenameLowerCase = filename.toLowerCase();
			// search for substring
			for(String token : searchKeywords){
				if(filenameLowerCase.indexOf(token) != -1){
					// hit!
					Float hitRate = new Float(1.0f/(float)searchKeywords.size());
					resultFile = new File(file);
					resultFile.setPath(filename);
					resultList.put(resultFile, hitRate);
					System.out.println(" * Search hit! Filename: "+filename+", hit-rate: "+hitRate);
					continue;
				}
			}
			
		}
		System.out.println("[MODEL] Search done!");
		System.out.println();
		// send resultList to Controller
		CC_Model_SearchResults command = new CC_Model_SearchResults(resultList, searchID);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Sends to the Controller a copy of the list of shared files
	 */
	private void getSharedFilesList(){
		System.out.println("[MODEL] Entering getSharedFilesList...");
		List<File> resultList = new ArrayList<File>();
		for(ListIterator<File> it = sharedFiles.listIterator(); it.hasNext();){
			resultList.add(new File(it.next()));
		}
		// send resultList to Controller
		CC_Model_SharedFilesList command = new CC_Model_SharedFilesList(resultList);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Adds a file to the list of shared files
	 * @param file a file to be added
	 */
	private void addSharedFile(File file){
		System.out.println("[MODEL] Entering addSharedFile...");
		FileHandler fh = new FileHandler(file);
		if(!fh.fileExists()){
			System.err.println("[MODEL] Error in addSharedFile: the file "+file.getPath()+" doesn't exist!");
			return;
		}
		if(!fh.canRead()){
			System.err.println("[MODEL] Error in addSharedFile: the file "+file.getPath()+" cannot be read!");
			return;
		}
		file.setHash(fh.getMD5sum());
		file.setSize(fh.getSize());
		if(sharedFiles.contains(file)){
			System.err.println("[MODEL] Error in addSharedFile: the file "+file.getPath()+" is already shared!");
			return;
		}
		sharedFiles.add(file);
		// send updated list to controller
		getSharedFilesList();
	}
	
	
	/**
	 * Removes the file from the list of shared files
	 * @param file a file to be removed from list (a path is required to be filled)
	 */
	private void removeSharedFile(File file){
		System.out.println("[MODEL] Entering removeSharedFile...");
		File removeFile = null;
		for(File f : sharedFiles){
			if(f.getPath().equals(file.getPath()))
				removeFile = f;
		}
		if(removeFile == null){
			System.err.println("[MODEL] Error in addSharedFile: the file "+file.getPath()+" doesn't exist on the list of shared files!");
			return;
		}
		sharedFiles.remove(removeFile);
		// send updated list to controller
		getSharedFilesList();
	}

	
	/**
	 * Sends to the Controller a copy of the list of download files
	 */
	private void getDownloadFilesList(){
		System.out.println("[MODEL] Entering getDownloadFilesList...");
		List<DownloadFile> resultList = new ArrayList<DownloadFile>();
		for(ListIterator<ModelDownloadFile> it = downloadFiles.listIterator(); it.hasNext();){
			resultList.add(new DownloadFile(it.next()));
		}
		// send resultList to Controller
		CC_Model_DownloadFilesList command = new CC_Model_DownloadFilesList(resultList);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	
	/**
	 * Adds a file to the list of download files
	 * @param file a file to be added
	 */
	private void addDownloadFile(File file){
		System.out.println("[MODEL] Entering addDownloadFile...");
		if(downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in addDownloadFile: the file "+file.getPath()+" already exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = new ModelDownloadFile(file.getPath(), file.getSize(), file.getHash(), DownloadStateType.WAITING_FOR_HOSTS);
		downloadFiles.add(dFile);
		FileHandler fh = new FileHandler(new File(dFile));
		if(fh.fileExists())
			fh.deleteFile();
		if(!fh.create()){
			dFile.setDownloadState(DownloadStateType.STOPPED);
			System.err.println("[MODEL] Error in addDownloadFile: the file "+file.getPath()+" cannot be created!");
		}
		this.checkDownloadFilesList();
		// send complete list of download files
		this.getDownloadFilesList();
	}
	
	
	/**
	 * Removes a file from list of download files and from the disk
	 * @param file a file to be removed from list
	 */
	private void removeDownloadFile(File file){
		System.out.println("[MODEL] Entering removeDownloadFile...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in removeDownloadFile: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ListIterator<ModelDownloadFile> it = downloadFiles.listIterator(downloadFiles.indexOf(file));
		ModelDownloadFile toRemove = it.next(); //downloadFiles.get(downloadFiles.indexOf(file));
		FileHandler fh = new FileHandler(toRemove);
		if(!fh.deleteFile()){
			System.err.println("[MODEL] Error in removeDownloadFile: unable to delete a file: "+toRemove.getPath());
		}				
		it.remove();
		this.checkDownloadFilesList();
		// send complete list of download files
		this.getDownloadFilesList();
	}
	
	
	/**
	 * Unblocks file marked as STOPPED
	 * @param file
	 */
	private void startDownloadFile(File file){
		System.out.println("[MODEL] Entering startDownloadFile...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in startDownloadFile: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		if(dFile.getDownloadState() != DownloadStateType.STOPPED){
			System.err.println("[MODEL] Error in startDownloadFile: the file "+file.getPath()+" isn't marked as STOPPED!");
			return;
		}
		dFile.setDownloadState(DownloadStateType.WAITING);
		this.checkDownloadFilesList();
		// send complete list of download files
		this.getDownloadFilesList();
	}
	
	
	/**
	 * Marks a download file as STOPPED
	 * @param file
	 */
	private void stopDownloadFile(File file){
		System.out.println("[MODEL] Entering stopDownloadFile...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in stopDownloadFile: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		if(dFile.getDownloadState() == DownloadStateType.STOPPED){
			System.err.println("[MODEL] Error in stopDownloadFile: the file "+file.getPath()+" is already marked as STOPPED!");
			return;
		}
		
		if(dFile.getDownloadState() == DownloadStateType.ACTIVE){
			dFile.setDownloadState(DownloadStateType.STOPPED);
			// inform controller about state change
			DownloadFile returnFile = new DownloadFile(dFile);
			CC_Model_DownloadFileStateChanged command = new CC_Model_DownloadFileStateChanged(returnFile, DownloadStateType.ACTIVE);
			try{
				controller.writeMsg(command);
			} catch(InterruptedException e){
				System.err.println("[MODEL] Error while sending a command to Controller!");
			}
		}
		dFile.setDownloadState(DownloadStateType.STOPPED);
		
		this.checkDownloadFilesList();
		// send complete list of download files
		this.getDownloadFilesList();
	}
	
	
	/**
	 * Moves a download file to a specified position
	 * @param file a file to be moved
	 * @param position a new position of the file
	 */
	private void moveDownloadFile(File file, int position){
		System.out.println("[MODEL] Entering moveDownloadFile... Position: "+position);
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in moveDownloadFile: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		if(position > downloadFiles.size()){
			System.err.println("[MODEL] Error in moveDownloadFile: position "+position+" is greater than number of items.");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		downloadFiles.remove(downloadFiles.indexOf(file));
		// add in the proper place
		ListIterator<ModelDownloadFile> it = downloadFiles.listIterator();
		for(int i=0; i<position; ++i){
			it.next();
		}
		it.add(dFile);
		this.checkDownloadFilesList();
		// send complete list of download files
		this.getDownloadFilesList();
	}
	
	
	/**
	 * Returns list of hosts of the specified download file.
	 * @param file
	 */
	private void getDownloadFileHostsList(File file){
		System.out.println("[MODEL] Entering getDownloadFileHostsLists...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in getDownloadFileHostsList: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		List<Host> hostList = dFile.getHostsList();
		List<Host> resultList = new ArrayList<Host>();
		for(ListIterator<Host> it = hostList.listIterator(); it.hasNext();){
			resultList.add(new Host(it.next()));
		}
		// send resultList to Controller
		DownloadFile returnFile = new DownloadFile(dFile);
		CC_Model_DownloadFileHostsList command = new CC_Model_DownloadFileHostsList(returnFile, resultList);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Adds a host to the file's list of hosts.
	 * @param file
	 * @param host
	 */
	private void addDownloadFileHost(File file, Host host){
		System.out.println("[MODEL] Entering addDownloadFileHost...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in addDownloadFileHost: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		dFile.addHost(host);
		if(dFile.getDownloadState() == DownloadStateType.WAITING_FOR_HOSTS){
			this.checkDownloadFilesList();
			this.getDownloadFilesList();
		}
	}
	
	
	/**
	 * Removes a host from the file's list of hosts.
	 * @param file
	 * @param host
	 */
	private void removeDownloadFileHost(File file, Host host){
		System.out.println("[MODEL] Entering removeDownloadFileHost...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in removeDownloadFileHost: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		dFile.removeHost(host);
		if(dFile.isHostsListEmpty() && dFile.getDownloadState()==DownloadStateType.ACTIVE){
			this.checkDownloadFilesList();
			this.getDownloadFilesList();
		}
	}
	
	
	/**
	 * Informs the controller about file's next lacking part
	 * @param file
	 */
	private void getNextLackingPart(File file){
		System.out.println("[MODEL] Entering getNextLackingPart...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in getNextLackingPart: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		
		// Check if entire file has already been downloaded
		if(dFile.isLackingPartsListEmpty()){
			downloadFiles.remove(dFile);
			// inform controller about finished download
			DownloadFile returnFile = new DownloadFile(dFile);
			CC_Model_DownloadFinished command = new CC_Model_DownloadFinished(returnFile);
			try{
				controller.writeMsg(command);
			} catch(InterruptedException e){
				System.err.println("[MODEL] Error while sending a command to Controller!");
			}
			// send files list
			getDownloadFilesList();
			return;
		}
		
		int nextLackingPart = dFile.getNextLackingPart();
		// send nextLackingPart to the controller
		DownloadFile returnFile = new DownloadFile(dFile);
		CC_Model_NextLackingPart command = new CC_Model_NextLackingPart(returnFile, nextLackingPart);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Informs the controller about next host hosting that file
	 * @param file
	 */
	private void getNextHost(File file){
		System.out.println("[MODEL] Entering getNextHost...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in getNextHost: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		Host nextHost = dFile.getNextHost();
		Host resultHost;
		if(nextHost == null)
			resultHost = null;
		else
			resultHost = new Host(nextHost);
		// send nextHost
		DownloadFile returnFile = new DownloadFile(dFile);
		CC_Model_NextHost command = new CC_Model_NextHost(returnFile, resultHost);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Gets specified part of a file and sends it to the controller.
	 * @param file a file to be read
	 * @param partNumber a number of part of the file
	 */
	private void getPartOfFile(File file, int partNumber){
		System.out.println("[MODEL] Entering getPartOfFile...");
		if(!sharedFiles.contains(file)){
			System.err.println("[MODEL] Error in getPartOfFile: a file is not shared!");
			return;
		}
		File sFile = new File(sharedFiles.get(sharedFiles.indexOf(file)));
		if(sFile.getSize() < partNumber*partSize){
			System.err.println("[MODEL] Error in getPartOfFile: a number of part is too big!");
			return;
		}
		FileHandler fileH = new FileHandler(sFile);
		byte[] data = fileH.getPartOfFile(partNumber);
		if(data == null){
			System.err.println("[MODEL] Error in getPartOfFile: error while reading a file "+sFile.getPath()+"!");
			return;
		}
		// send data to controller
		CC_Model_PartOfFile command = new CC_Model_PartOfFile(sFile, partNumber, data);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Saves a part of a download file.
	 * @param file
	 * @param partNumber
	 * @param data
	 */
	private void savePartOfFile(File file, int partNumber, byte[] data){
		System.out.println("[MODEL] Entering savePartOfFile...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in savePartOfFile: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		if(!dFile.isLacking(partNumber)){
			System.err.println("[MODEL] Error in savePartOfFile: the part number "+partNumber+" of the file "+file.getPath()+" has already been downloaded!");
			return;
		}
		
		FileHandler fh = new FileHandler(dFile);
		if(!fh.fileExists()){
			if(!fh.create()){
				System.err.println("[MODEL] Error in savePartOfFile: cannot create a file: "+dFile.getPath());
				dFile.setDownloadState(DownloadStateType.STOPPED);
				return;
			}
			dFile.fillAllLackingParts();
		}
		if(fh.savePartOfFile(data, partNumber))
			dFile.removeLackingPart(partNumber);
		else
			System.err.println("[MODEL] Error in savePartOfFile: unable to save a part of file: "+dFile.getPath());
	}
	
	
	/**
	 * Saves current configuration.
	 */
	private void saveConfiguration(){
		System.out.println("[MODEL] Entering saveConfiguration...");
		configManager.saveGlobalSettings(globalSettings);
		configManager.saveDownloadFilesList(downloadFiles);
		configManager.saveSharedFilesList(sharedFiles);
		// send to controller a confirmation
		ControllerCommand command = new ControllerCommand(ControllerCommand.ControllerCommandType.MODEL_CONFIGURATION_SAVED);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Informs controller about the maximum number of active connections.
	 */
	private void getMaxActiveConnectionsNumber(){
		System.out.println("[MODEL] Entering getMaxActiveConnectionsNumber...");
		// send to controller maxActiveDownloadsNumber
		CC_Model_MaxActiveConnectionsNumber command = new CC_Model_MaxActiveConnectionsNumber(globalSettings.getMaxActiveDownloadsNumber(), globalSettings.getMaxConnectionsPerDownload());
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	
	/**
	 * Sets the maximum number of active connections. Updates list of download files.
	 * @param n new maximum
	 */
	private void setMaxActiveConnectionsNumber(int maxDownloads, int maxConnectionsPerDownload){
		System.out.println("[MODEL] Entering setMaxActiveConnectionsNumber...");
		globalSettings.setMaxActiveDownloadsNumber(maxDownloads);
		globalSettings.setMaxConnectionsPerDownload(maxConnectionsPerDownload);
		this.checkDownloadFilesList();
	}
	
	
	/**
	 * Clears the list of hosts of the specified download file.
	 * @param file a download file of which list of hosts is to be cleared
	 */
	private void clearDownloadFileHostsList(File file){
		System.out.println("[MODEL] Entering clearDownloadFileHostsList...");
		if(!downloadFiles.contains(file)){
			System.err.println("[MODEL] Error in clearDownloadFileHostsList: the file "+file.getPath()+" doesn't exists on the list of download files!");
			return;
		}
		ModelDownloadFile dFile = downloadFiles.get(downloadFiles.indexOf(file));
		dFile.clearHostsList();
		if(dFile.getDownloadState() == DownloadStateType.ACTIVE){
			this.checkDownloadFilesList();
			this.getDownloadFilesList();
		}
	}
	
	
	/**
	 * Checks if the specified file exists on the list of shared files.
	 * @param file a file to be checked
	 */
	private void isSharedFile(File file, int id){
		System.out.println("[MODEL] Entering isSharedFile...");
		boolean result = false;
		if(sharedFiles.contains(file))
			result = true;
		// send  response to controller
		CC_Model_IsSharedFileResponse command = new CC_Model_IsSharedFileResponse(file, result, id);
		try{
			controller.writeMsg(command);
		} catch(InterruptedException e){
			System.err.println("[MODEL] Error while sending a command to Controller!");
		}
	}
	
	/**
	 * Checks the list of download files if the order (ACTIVE|STOPPED){0,maxActiveDownloadsNumber}(WAITING|STOPPED)* is correct.
	 * If not, updates the list.
	 */
	private void checkDownloadFilesList(){
		int activeDownloads = 0;
		for(ListIterator<ModelDownloadFile> it = downloadFiles.listIterator(); it.hasNext();){
			ModelDownloadFile file = it.next();
			switch(file.getDownloadState()){
			case STOPPED:
				continue;
			case ACTIVE:
				if(activeDownloads == globalSettings.getMaxActiveDownloadsNumber()){
					file.setDownloadState(DownloadStateType.WAITING);
					// inform controller about state change
					DownloadFile returnFile = new DownloadFile(file);
					CC_Model_DownloadFileStateChanged command = new CC_Model_DownloadFileStateChanged(returnFile, DownloadStateType.ACTIVE);
					try{
						controller.writeMsg(command);
					} catch(InterruptedException e){
						System.err.println("[MODEL] Error while sending a command to Controller!");
					}
				}
				else if(file.isHostsListEmpty()){
					file.setDownloadState(DownloadStateType.WAITING_FOR_HOSTS);
					// inform controller about state change
					DownloadFile returnFile = new DownloadFile(file);
					CC_Model_DownloadFileStateChanged command = new CC_Model_DownloadFileStateChanged(returnFile, DownloadStateType.ACTIVE);
					try{
						controller.writeMsg(command);
					} catch(InterruptedException e){
						System.err.println("[MODEL] Error while sending a command to Controller!");
					}
				}
				else
					++activeDownloads;
				break;
			case WAITING_FOR_HOSTS:
				if(activeDownloads < globalSettings.getMaxActiveDownloadsNumber() && !file.isHostsListEmpty()){
					file.setDownloadState(DownloadStateType.ACTIVE);
					++activeDownloads;
					// inform controller about state change
					DownloadFile returnFile = new DownloadFile(file);
					CC_Model_DownloadFileStateChanged command = new CC_Model_DownloadFileStateChanged(returnFile, DownloadStateType.WAITING_FOR_HOSTS);
					try{
						controller.writeMsg(command);
					} catch(InterruptedException e){
						System.err.println("[MODEL] Error while sending a command to Controller!");
					}
				}
			case WAITING:
				if(activeDownloads < globalSettings.getMaxActiveDownloadsNumber()){
					if(file.isHostsListEmpty())
						file.setDownloadState(DownloadStateType.WAITING_FOR_HOSTS);
					else
						file.setDownloadState(DownloadStateType.ACTIVE);
					++activeDownloads;
					// inform controller about state change
					DownloadFile returnFile = new DownloadFile(file);
					CC_Model_DownloadFileStateChanged command = new CC_Model_DownloadFileStateChanged(returnFile, DownloadStateType.WAITING);
					try{
						controller.writeMsg(command);
					} catch(InterruptedException e){
						System.err.println("[MODEL] Error while sending a command to Controller!");
					}
				}
			}
		}
	}
}
